package cc.gpai.data_stru.bag;

import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

public abstract class AbstractBag<E> extends AbstractCollection<E> implements Bag<E> {

    @Override
    public boolean addAll(Collection<? extends E> c) {
        if (c instanceof Bag<?>) {
            if (c instanceof AbstractBag<?>) {
                @SuppressWarnings("unchecked")
                AbstractBag<E> that = (AbstractBag<E>) c;
                Set<Entry<E, AtomicLong>> set = that.entrySet();
                for (Entry<E, AtomicLong> entry : set) {
                    E e = entry.getKey();
                    modCount(e, entry.getValue().get());
                }
            } else {
                Bag<? extends E> that = (Bag<? extends E>) c;
                Set<? extends E> set = that.uniqueSet();
                for (E e : set) {
                    modCount(e, that.getCount(e));
                }
            }
        } else {
            for (E e : c) {
                add(e);
            }
        }
        return true;
    }

    @SuppressWarnings("unchecked")
    @Override
    public long getCount(Object obj) {
        return modCount((E) obj, 0);
    }

    public abstract Set<Entry<E, AtomicLong>> entrySet();

    @Override
    public boolean isEmpty() {
        return uniqueSet().isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return uniqueSet().contains(o);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        if (c instanceof Bag) {
            return uniqueSet().containsAll(((Bag) c).uniqueSet());
        }
        for (Object obj : c) {
            if (!contains(obj)) {
                return false;
            }
        }
        return true;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        boolean modified = false;
        if (c instanceof Bag<?>) {
            if (c instanceof AbstractBag<?>) {
                @SuppressWarnings("unchecked")
                AbstractBag<E> that = (AbstractBag<E>) c;
                Set<Entry<E, AtomicLong>> set = that.entrySet();
                for (Entry<E, AtomicLong> entry : set) {
                    E e = entry.getKey();
                    if (!modified) {
                        modified |= contains(e);
                    }
                    modCount(e, -entry.getValue().get());
                }
            } else {
                @SuppressWarnings("unchecked")
                Bag<? extends E> that = (Bag<? extends E>) c;
                Set<? extends E> set = that.uniqueSet();
                for (E e : set) {
                    if (!modified) {
                        modified |= contains(e);
                    }
                    modCount(e, -that.getCount(e));
                }
            }
        } else {
            for (Object obj : c) {
                modified |= remove(obj);
            }
        }
        return modified;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        boolean modified = false;
        if (c instanceof Bag<?>) {
            Bag<?> that = (Bag<?>) c;
            for (E e : uniqueSet()) {
                setCount(e, Math.min(getCount(e), that.getCount(e)));
            }
        } else {
            for (E e : uniqueSet()) {
                if (!c.contains(e)) {
                    modified |= remove(e);
                }
            }
        }
        return modified;
    }

    @Override
    public int size() {
        int sum = 0;
        for (E e : uniqueSet()) {
            sum += getCount(e);
        }
        return sum;
    }

    @Override
    public Iterator<E> iterator() {
        return new BagIterator<E>(this);
    }
}

class BagIterator<E> implements Iterator<E> {

    private final Bag<E> bag;
    private final Iterator<E> itr;
    private E e;
    private long l;

    public BagIterator(Bag<E> bag) {
        super();
        this.bag = bag;
        itr = bag.uniqueSet().iterator();
        doNext();
    }

    private void doNext() {
        e = itr.next();
        l = bag.getCount(e);
    }

    private void step() {
        if (l < 1) {
            doNext();
        }
        l--;
    }

    @Override
    public boolean hasNext() {
        if (l > 0) {
            return true;
        }
        return itr.hasNext();
    }

    @Override
    public E next() {
        step();
        return e;
    }

    @Override
    public void remove() {
        bag.remove(e);
        step();
    }
}